package hamaster.jpbc.mgr;

import hamaster.jpbc.Main;
import hamaster.jpbc.crypt.Crypt;
import hamaster.jpbc.crypt.CryptIO;
import hamaster.jpbc.crypt.aes.AES;
import hamaster.jpbc.crypt.aes.OperationMode;
import hamaster.jpbc.crypt.aes.Rijndael;
import hamaster.jpbc.crypt.impl.SimpleCryptIOImpl;
import hamaster.jpbc.crypt.rc4.RC4;
import hamaster.jpbc.exception.CryptException;
import hamaster.jpbc.gui.DecryptPanel;
import hamaster.jpbc.pbc.CipherText;
import hamaster.jpbc.pbc.PBC;
import hamaster.jpbc.pbc.PBCDriver;
import hamaster.jpbc.pbc.PlainText;
import hamaster.jpbc.pbc.io.IBCIOException;
import hamaster.jpbc.pbc.io.ID;
import hamaster.jpbc.pbc.io.IDIO;
import hamaster.jpbc.pbc.io.SimpleIDFileIOImpl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Properties;

import javax.swing.JOptionPane;

public class SymmetricDecryptManager implements Runnable {

	protected File meta;
	protected File idFile;
	protected File saveFolder;
	protected DecryptPanel callback;

	public SymmetricDecryptManager(File meta, File idFile, File saveFolder, DecryptPanel callback) {
		this.meta = meta;
		this.idFile = idFile;
		this.saveFolder = saveFolder;
		this.callback = callback;
	}

	@Override
	public void run() {
		ID id;
		try {
			IDIO idio = new SimpleIDFileIOImpl(idFile);
			id = idio.load();
		} catch (IBCIOException e) {
			callback.message(Messages.getString("SymmetricDecryptManager.cannotReadID"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		}
		Properties metadata = new Properties();
		try {
			metadata.load(new FileInputStream(meta));
		} catch (IOException e) {
			callback.message(Messages.getString("SymmetricDecryptManager.cannotReadMeta"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		}
		CipherText symKey = new CipherText(metadata, SymmetricEncryptManager.SYMMETRIC_KEY_PREFIX);
		PBC pbc = PBC.instance();
		PlainText plain = pbc.decrypt(symKey, id.getPrivateKey());
		byte[] pbcBuffer = plain.getElement().toBytes();
		byte[] symKeyBytes = new byte[symKey.getLength()];
		System.arraycopy(pbcBuffer, pbcBuffer.length - symKey.getLength(), symKeyBytes, 0, symKey.getLength());
		long plainLength = Long.parseLong(metadata.getProperty(SymmetricEncryptManager.PLAIN_FILE_LENGTH));
		CryptIO io = new SimpleCryptIOImpl(plainLength, true);
		Crypt crypt = null;
		String crypto = new String(PBCDriver.unhex(metadata.getProperty(SymmetricEncryptManager.SYMMETRIC_KEY)));
		if ("AES".equalsIgnoreCase(crypto)) { //$NON-NLS-1$
			crypt = new AES();
			if (metadata.containsKey(SymmetricEncryptManager.OPERATION_MODE_KEY)) {
				try {
					String modeKey = new String(PBCDriver.unhex(metadata.getProperty(SymmetricEncryptManager.OPERATION_MODE_KEY)));
					OperationMode mode = Enum.valueOf(OperationMode.class, modeKey);
					byte[] iv = new byte[Rijndael.AES_BLOCK_SIZE];
					System.arraycopy(pbcBuffer, pbcBuffer.length - symKey.getLength() - Rijndael.AES_BLOCK_SIZE, iv, 0, Rijndael.AES_BLOCK_SIZE);
					((AES) crypt).setOperationMode(mode);
					((AES) crypt).setInitializationVector(iv);
				} catch (IllegalArgumentException e) {
				}
			}
			crypt.setBlockSize(Main.ioBlockSize());
		} else if("RC4".equalsIgnoreCase(crypto)) { //$NON-NLS-1$
			crypt = new RC4();
		} else {
			callback.message(Messages.getString("SymmetricEncryptManager.unsupportedMethod"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		}
		io.setBlockSize(Main.ioBlockSize());
		crypt.setKey(symKeyBytes);
		io.setCrypt(crypt);
		io.setProgressListener(callback);
		String filename;
		String cipherName;
		try {
			filename = new String(PBCDriver.unhex(metadata.getProperty(SymmetricEncryptManager.PLAIN_FILE_NAME)), "UTF-8"); //$NON-NLS-1$
			cipherName = new String(PBCDriver.unhex(metadata.getProperty(SymmetricEncryptManager.CIPHER_NAME_KEY)), "UTF-8"); //$NON-NLS-1$
		} catch (UnsupportedEncodingException e) {
			filename = new String(PBCDriver.unhex(metadata.getProperty(SymmetricEncryptManager.PLAIN_FILE_NAME)));
			cipherName = new String(PBCDriver.unhex(metadata.getProperty(SymmetricEncryptManager.CIPHER_NAME_KEY)));
		}
		File plainMessage = new File(saveFolder, filename);
		File cipher = new File(meta.getParentFile(), cipherName);
		if (!cipher.exists()) {
			String format = Messages.getString("SymmetricDecryptManager.4"); //$NON-NLS-1$
			callback.message(String.format(format, filename), JOptionPane.ERROR_MESSAGE);
			return;
		}
		if (!cipher.canRead()) {
			String format = Messages.getString("SymmetricDecryptManager.5"); //$NON-NLS-1$
			callback.message(String.format(format, filename), JOptionPane.ERROR_MESSAGE);
			return;
		}
		try {
			OutputStream out = new FileOutputStream(plainMessage);
			io.decrypt(cipher, out);
		} catch (FileNotFoundException e) {
			callback.message(Messages.getString("SymmetricDecryptManager.cannotReadCipher"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		} catch (IOException e) {
			callback.message(Messages.getString("SymmetricDecryptManager.cannotWritePlain"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		} catch (CryptException e) {
			callback.message(Messages.getString("SymmetricDecryptManager.cryptoError"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		}
		if ("true".equalsIgnoreCase(metadata.getProperty(SymmetricEncryptManager.SIGNED_MARK))) { //$NON-NLS-1$
			callback.message(Messages.getString("SymmetricDecryptManager.verifySignature"), JOptionPane.INFORMATION_MESSAGE); //$NON-NLS-1$
			callback.enableVerify(plainMessage, metadata);
		} else {
			callback.message(Messages.getString("SymmetricDecryptManager.decryptComplete"), JOptionPane.INFORMATION_MESSAGE); //$NON-NLS-1$
		}
	}
}
